import csv

from urllib import quote_plus
from google.appengine.api import urlfetch
from google.appengine.ext import db
from google.appengine.api import users

from VizSpread.appengine_utilities import sessions
from VizSpread.appengine import models as vizmodels
from VizSpread import GMAPS_API_KEY, GMAPS_GEOCODE_SERVICE_URL

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class DuplicateDatasetError(Error):
    """Exception raised for when an attempt to create a dataset with 
	the same name as an existing dataset is made.

    Attributes:
        name -- the name of the duplicate dataset
        message -- explanation of the error
    """

    def __init__(self, name, message):
        self.name = name
        self.message = message

class ContactsParser():
	"""
	A class to parse contacts data in CSV format and return it in a form that
	can be used by Google's maps API to obtain latitude and longitude values
	"""
	def __init__(self, dataSetName, dataCentre, contactsData):
		self.dataSetName = dataSetName
		self.dataCentre = dataCentre
		self.contactsData = str(contactsData)
		
	def isValidData(self):
		"""
		Performs checks to verify whether or not the data is 'valid' CSV 
		and in the right fomrat (i.e. contains the required headers)
		"""

		if not self.contactsData:
			return False

		sniffer = csv.Sniffer()
		has_header = sniffer.has_header(self.contactsData)
		if not has_header:
			return False

		contactsList = self.contactsData.split('\n')
		reader = csv.reader(contactsList)
		headerRow = reader.next()

		if not "Name" in headerRow:
			return False
		if not "Address1" in headerRow:
			return False
		if not "Town/City" in headerRow:
			return False
		if not "County/State" in headerRow:
			return False
		if not "Zip/Post Code" in headerRow:
			return False
		if not "Country" in headerRow:
			return False

		return True

	def getFieldHeaderPositions(self, headers):
		field_header_positions = {'fn':-1, 'email':-1, 'street-address':-1, 'locality':-1, 'region':-1, 'postal-code':-1, 'country-name':-1, 'tel':-1}
		headerNum = 0
		for header in headers:
			if header == 'Name':
				field_header_positions['fn'] = headerNum
			if header == 'Email':
				field_header_positions['email'] = headerNum
			if header == 'Address1':
				field_header_positions['street-address'] = headerNum
			if header == 'Town/City':
				field_header_positions['locality'] = headerNum
			if header == 'County/State':
				field_header_positions['region'] = headerNum
			if header == 'Zip/Post Code':
				field_header_positions['postal-code'] = headerNum
			if header == 'Country':
				field_header_positions['country-name'] = headerNum
			if header == 'Tel':
				field_header_positions['tel'] = headerNum
			headerNum = headerNum + 1
		return field_header_positions

	def parseAndStoreGeocodeData(self):
		"""
		Parses a dataset to extract any valid contact data, geocode it and set
		up the objects in the data store
		"""
		contactsList = self.contactsData.split('\n')
		reader = csv.reader(contactsList)
		parseSuccess = True
		field_header_positions = []
		
		try:
			dataset = self.createDataset()
		except DuplicateDatasetError:
			parseSuccess = False
			return parseSuccess
		except db.NotSavedError:
			parseSuccess = False
			return parseSuccess
			
		for row in reader:
			
			if reader.line_num == 1: # retrieve the headers
				headers = row
				field_header_positions = self.getFieldHeaderPositions(headers)
				continue

			adr = ''
			
			try:
				street_address = row[field_header_positions['street-address']]
				locality = row[field_header_positions['locality']],
				region = row[field_header_positions['region']],
				postal_code = row[field_header_positions['postal-code']],
				country_name = row[field_header_positions['country-name']]
				
				if street_address == '':
					continue

				if street_address:
					adr += street_address
				if locality:
					adr += ', %s' % locality					
				if region:
					adr += ', %s' % region
				if postal_code:
					adr += ', %s' % postal_code
				if country_name:
					adr += ', %s' % country_name

			except IndexError:
				continue
			
			encoded_adr = quote_plus(str(adr))
			url = "%s?q=%s&output=json&oe=utf8&sensor=false&key=%s" % (GMAPS_GEOCODE_SERVICE_URL, encoded_adr, GMAPS_API_KEY)
			response = urlfetch.fetch(url)

			if response.status_code == 200:
				contactName = ''
				contactEmail = ''
				
				if users.get_current_user(): # only store the contact's name and email address if the current user is signed in
					contactName = row[field_header_positions['fn']]
					contactEmail = row[field_header_positions['email']]
					
				contact = self.createContact(dataset, contactName, contactEmail, response.content)
			else:
				# DO SOMETHING TO RECOVER FROM THIS PROBLEM
				print "Error retrieving data from google maps"
		
		return parseSuccess		

	def createDataset(self):
		
		try: # make sure there isn't an existing dataset with the same name
			fetched_dataset = vizmodels.Dataset.all().filter('name =', self.dataSetName).fetch(1)[0]
		except IndexError:
			user = users.get_current_user()
			session = sessions.Session()
			sessionId = session.sid
			    
			dataset = vizmodels.Dataset(name = self.dataSetName,
										dataCentre = self.dataCentre,
										dataCSV = self.contactsData,
										owner = user,
										sessionId = sessionId)
			try:
				dataset.put()
			except db.NotSavedError:
				raise
				return
			else:
				sets = session['sets']
				sets.append(sessionId)
				session['sets'] = sets
				return dataset
		else:
			raise DuplicateDatasetError(self.dataSetName, "Duplicate dataset")

	def createContact(self, dataset, contactName, contactEmail, contactGeoDataJSON):
	
		contactGeoData = eval(contactGeoDataJSON)
		place = contactGeoData["Placemark"][0]
		lat = place["Point"]["coordinates"][1]
		lng = place["Point"]["coordinates"][0]
		contact = vizmodels.Contact(set = dataset,
									name = contactName,
									email = contactEmail,
									address = place["address"],
									geocodeResultJsonOb = contactGeoDataJSON,
									geopoint = "%s,%s" % (lat, lng))

		try:
			contact.put()
		except db.NotSavedError:
			#print contactGeoDataJSON
			print "NotSaved"
		#except:
		#	print "Error"
		else:
			return contact


